#ifndef JEGEO_QTADAPTOR_H
#define JEGEO_QTADAPTOR_H
#include <QDebug>
#include <QPainterPath>
#include <QRectF>
#include <QStringList>

#include "jegeo/common.hpp"
namespace jegeo {

//constexpr double PIXEL_INCH_RATIO = 1E7;
constexpr double PIXEL_INCH_RATIO = 10160000;

namespace jemath = jegeo::math;

namespace point {
template<typename T>
QPointF to_qpoint(const Point<T> &point)
{
    return QPointF(std::get<0>(point), std::get<1>(point));
}

template<typename T>
Point<T>& transform(Point<T> &point, const QTransform &trans)
{
    jereal tx , ty;
    trans.map(static_cast<jereal>(jegeo::px(point)), static_cast<jereal>(jegeo::py(point)), &tx, &ty);
    T maxval = std::numeric_limits<T>::max();
    T minval = std::numeric_limits<T>::min();
    tx = jemath::clamp<jereal>(tx, minval, maxval);
    ty = jemath::clamp<jereal>(ty, minval, maxval);

    jegeo::set_px(point, jemath::roundcast<T>(tx));
    jegeo::set_py(point, jemath::roundcast<T>(ty));
    return point;
}

template<typename T>
Point<T> transformed(const Point<T> &point, const QTransform &trans)
{
    Point<T> res = point;
    transform(res, trans);
    return res;
}

} // namespace point

namespace line {
template<typename T>
QLineF to_qline(const Line<T> &line)
{
    return QLineF(std::get<0>(line), std::get<1>(line), std::get<2>(line), std::get<3>(line));
}

template<typename T>
Line<T>& transform(Line<T> &line, const QTransform &trans)
{
    jereal txs, tys, txe, tye;
    trans.map(static_cast<jereal>(jegeo::psx(line)), static_cast<jereal>(jegeo::psy(line)), &txs, &tys);
    trans.map(static_cast<jereal>(jegeo::pex(line)), static_cast<jereal>(jegeo::pey(line)), &txe, &tye);
    T maxval = std::numeric_limits<T>::max();
    T minval = std::numeric_limits<T>::min();
    txs = jemath::clamp<jereal>(txs, minval, maxval);
    tys = jemath::clamp<jereal>(tys, minval, maxval);
    txe = jemath::clamp<jereal>(txe, minval, maxval);
    tye = jemath::clamp<jereal>(tye, minval, maxval);

    jegeo::set_psx(line, jemath::roundcast<T>(txs));
    jegeo::set_psy(line, jemath::roundcast<T>(tys));
    jegeo::set_pex(line, jemath::roundcast<T>(txe));
    jegeo::set_pey(line, jemath::roundcast<T>(tye));
    return line;
}

template<typename T>
Line<T> transformed(const Line<T> &line, const QTransform &trans)
{
    Line<T> res = line;
    transform(res, trans);
    return res;
}


} // namespace line

namespace segment {
template<typename T>
QLineF to_qline(const Segment<T> &segment)
{
    return QLineF(std::get<0>(segment), std::get<1>(segment), std::get<2>(segment), std::get<3>(segment));
}

template<typename T>
Segment<T>& transform(Segment<T> &segment, const QTransform &trans)
{
    jereal txs, tys, txe, tye;
    trans.map(static_cast<jereal>(jegeo::psx(segment)), static_cast<jereal>(jegeo::psy(segment)), &txs, &tys);
    trans.map(static_cast<jereal>(jegeo::pex(segment)), static_cast<jereal>(jegeo::pey(segment)), &txe, &tye);
    T maxval = std::numeric_limits<T>::max();
    T minval = std::numeric_limits<T>::min();
    txs = jemath::clamp<jereal>(txs, minval, maxval);
    tys = jemath::clamp<jereal>(tys, minval, maxval);
    txe = jemath::clamp<jereal>(txe, minval, maxval);
    tye = jemath::clamp<jereal>(tye, minval, maxval);

    jegeo::set_psx(segment, jemath::roundcast<T>(txs));
    jegeo::set_psy(segment, jemath::roundcast<T>(tys));
    jegeo::set_pex(segment, jemath::roundcast<T>(txe));
    jegeo::set_pey(segment, jemath::roundcast<T>(tye));
    return segment;
}

template<typename T>
Segment<T> transformed(const Segment<T> &segment, const QTransform &trans)
{
    Segment<T> res = segment;
    transform(res, trans);
    return res;
}

} // namespace segment

namespace rect {
template<typename T>
QRectF to_qrect(const Rect<T> &rect)
{
    return QRectF(QPointF(std::get<0>(rect), std::get<1>(rect)), QPointF(std::get<2>(rect), std::get<3>(rect)));
}

template<typename T>
Rect<T>& transform(Rect<T> &rect, const QTransform &trans)
{
    jereal tx1, ty1, tx2, ty2;
    trans.map(static_cast<jereal>(jegeo::px1(rect)), static_cast<jereal>(jegeo::py1(rect)), &tx1, &ty1);
    trans.map(static_cast<jereal>(jegeo::px2(rect)), static_cast<jereal>(jegeo::py2(rect)), &tx2, &ty2);
    T maxval = std::numeric_limits<T>::max();
    T minval = std::numeric_limits<T>::min();
    tx1 = jemath::clamp<jereal>(tx1, minval, maxval);
    ty1 = jemath::clamp<jereal>(ty1, minval, maxval);
    tx2 = jemath::clamp<jereal>(tx2, minval, maxval);
    ty2 = jemath::clamp<jereal>(ty2, minval, maxval);

    jegeo::set_px1(rect, jemath::roundcast<T>(tx1));
    jegeo::set_py1(rect, jemath::roundcast<T>(ty1));
    jegeo::set_px2(rect, jemath::roundcast<T>(tx2));
    jegeo::set_py2(rect, jemath::roundcast<T>(ty2));
    return rect;
}

template<typename T>
Rect<T> transformed(const Rect<T> &rect, const QTransform &trans)
{
    Rect<T> res = rect;
    transform(res, trans);
    return res;
}

} // namespace rect

namespace arc {
template<typename T>
Arc<T>& transform(Arc<T> &arc, const QTransform &trans)
{
    jereal txs, tys, txe, tye, txc, tyc;
    trans.map(static_cast<jereal>(jegeo::psx(arc)), static_cast<jereal>(jegeo::psy(arc)), &txs, &tys);
    trans.map(static_cast<jereal>(jegeo::pex(arc)), static_cast<jereal>(jegeo::pey(arc)), &txe, &tye);
    trans.map(static_cast<jereal>(jegeo::pcx(arc)), static_cast<jereal>(jegeo::pcy(arc)), &txc, &tyc);
    T maxval = std::numeric_limits<T>::max();
    T minval = std::numeric_limits<T>::min();
    txs = jemath::clamp<jereal>(txs, minval, maxval);
    tys = jemath::clamp<jereal>(tys, minval, maxval);
    txe = jemath::clamp<jereal>(txe, minval, maxval);
    tye = jemath::clamp<jereal>(tye, minval, maxval);
    txc = jemath::clamp<jereal>(txc, minval, maxval);
    tyc = jemath::clamp<jereal>(tyc, minval, maxval);

    jegeo::set_psx(arc, jemath::roundcast<T>(txs));
    jegeo::set_psy(arc, jemath::roundcast<T>(tys));
    jegeo::set_pex(arc, jemath::roundcast<T>(txe));
    jegeo::set_pey(arc, jemath::roundcast<T>(tye));
    jegeo::set_pcx(arc, jemath::roundcast<T>(txc));
    jegeo::set_pcy(arc, jemath::roundcast<T>(tyc));
    if (trans.m11() * trans.m22() - trans.m12() * trans.m21() < 0.0) {
        jegeo::set_cw(arc, !jegeo::cw(arc));
    }
    return arc;
}

template<typename T>
Arc<T> transformed(const Arc<T> &arc, const QTransform &trans)
{
    Arc<T> res = arc;
    transform(res,trans);
    return res;
}
} // namespace arc

namespace vertex {
template<typename T>
Vertex<T>& transform(Vertex<T> &vertex, const QTransform &trans)
{
    jereal tx , ty;
    trans.map(static_cast<jereal>(std::get<0>(vertex)), static_cast<jereal>(std::get<1>(vertex)), &tx, &ty);
    T maxval = std::numeric_limits<T>::max();
    T minval = std::numeric_limits<T>::min();
    tx = jemath::clamp<jereal>(tx, minval, maxval);
    ty = jemath::clamp<jereal>(ty, minval, maxval);

    std::get<0>(vertex) = jemath::roundcast<T>(tx);
    std::get<1>(vertex) = jemath::roundcast<T>(ty);
    jereal &angle = std::get<2>(vertex);
    if (angle != 0 && trans.m11() * trans.m22() - trans.m12() * trans.m21() < 0.0) {
        angle = -angle;
    }
    return vertex;
}

template<typename T>
Vertex<T> transformed(const Vertex<T> &vertex, const QTransform &trans)
{
    Vertex<T> res = vertex;
    transform(res, trans);
    return res;
}
} // namespace vertex

namespace polyline {
template<typename T>
Polyline<T> make_from_odblines(const QStringList &odblines, const jereal &scale = PIXEL_INCH_RATIO)
{
    std::function<T(const QString &)> toval = [&scale](const QString &s) { return jemath::roundcast<T, jereal>(s.toDouble() * scale); };

    Polyline<T> path;
    for (const auto &line : odblines) {
        auto parts = line.split(' ', Qt::SkipEmptyParts);
        if (parts.isEmpty())
            continue;
        auto cmd = parts[0].toUpper();
        if (cmd == "OS") {
            polyline_lineto(path, toval(parts[1]), toval(parts[2]));
        } else if (cmd == "OC") {
            polyline_arcto(path, toval(parts[1]), toval(parts[2]), toval(parts[3], toval(parts[4]), parts[5].toUpper() == "Y"));
        } else if (cmd == "OB") {
            polyline_moveto(path, toval(parts[1]), toval(parts[2]));
        } else if (cmd == "OE") {
            polyline_closepath(path);
        }
    }
    return path;
}

template<typename T>
QPainterPath to_qpainterpath(const Polyline<T> &arcpath, double scale)
{
    //qDebug() << "scale === " << scale;
    QPainterPath res;
    const auto &vertices = jegeo::polyline::vertices(arcpath);
    if (vertices.empty())
        return res;

    res.moveTo(scale * std::get<0>(vertices[0]), scale * std::get<1>(vertices[0]));
    for (size_t i = 1; i < vertices.size(); ++i) {
        const auto &v = vertices[i];
        T xe = std::get<0>(v);
        T ye = std::get<1>(v);
        if (jegeo::vertex::is_lineto(v)) {
            res.lineTo(scale * xe, scale * ye);
        } else if (jegeo::vertex::is_circle(v)) {
            const auto &prev = vertices[i - 1];
            T xs = std::get<0>(prev);
            T ys = std::get<1>(prev);
            T r = jemath::jehypot(xe - xs, ye - ys);
            res.addEllipse(scale * (xe - r), scale*(ye - r), 2.0 * scale * r, 2.0 * scale * r);
        } else if (jegeo::vertex::is_arcto(v)) {
            const auto &prev = vertices[i - 1];
            T xs = std::get<0>(prev);
            T ys = std::get<1>(prev);
            jereal angle = std::get<2>(v);
            bool cw = angle < 0.0;
            T xc, yc;
            jemath::arc_center_from_angle(xs, ys, xe, ye, angle, xc, yc);
            T r = jemath::jehypot(xc - xs, yc - ys);
            jereal sa = std::atan2(yc - ys, xs - xc);
            jereal ea = std::atan2(yc - ye, xe - xc);
            if (cw) {
                if (ea <= sa)
                    ea += jegeo::math::PI_MUL_2;
            } else {
                if (sa <= ea)
                    sa += jegeo::math::PI_MUL_2;
            }
            res.arcTo(scale * (xc - r), scale * (yc - r), scale * 2.0 * r, 2.0 * scale * r, jemath::rad2deg(sa), jemath::rad2deg(ea - sa));
        }
    }
    return res;
}

template<typename T>
Polyline<T>& transform(Polyline<T> &path, const QTransform &trans)
{
    for (auto &vertex : jegeo::polyline::vertices(path)) {
        jegeo::vertex::transform(vertex, trans);
    }
    return path;
}

template<typename T>
Polyline<T> transformed(const Polyline<T> &path, const QTransform &trans)
{
    Polyline<T> res = path;
    transform(res, trans);
    return res;

}
} // namespace polyline

namespace polygon {
template<typename T>
Polygon<T> make_from_odblines(const QStringList &odblines, const jereal &scale = PIXEL_INCH_RATIO)
{
    std::function<T(const QString &)> toval = [&scale](const QString &s) { return jemath::roundcast<T, jereal>(s.toDouble() * scale); };

    Polygon<T> polygon;
    bool is_polygon = false;
    for (const auto &line : odblines) {
        auto parts = line.split(' ', Qt::SkipEmptyParts);
        if (parts.isEmpty())
            continue;
        auto cmd = parts[0].toUpper();
        if (cmd == "S") {
            is_polygon = true;
        } else if (cmd == "SE") {
            break;
        }
        if (!is_polygon)
            continue;

        if (cmd == "OS") {
            jegeo::polygon::line_to(polygon, toval(parts[1]), toval(parts[2]));
        } else if (cmd == "OC") {
            jegeo::polygon::arc_to(polygon, toval(parts[1]), toval(parts[2]), toval(parts[3]), toval(parts[4]), parts[5].toUpper() == "Y");
        } else if (cmd == "OB") {
            jegeo::polygon::move_to(polygon, toval(parts[1]), toval(parts[2]), parts[3].toUpper() == "H");
        } else if (cmd == "OE") {
            jegeo::polygon::close_subpath(polygon);
        }
    }

    return polygon;
}

template<typename T>
QStringList to_odblines(const Polygon<T> polygon) {
    QStringList res;
    res << "S";
    for (const auto &poly: polygon) {
        const auto &vertices = jegeo::polyline::vertices(poly);
        const auto fv = vertices.front();
        res << QString("OB %1 %2 %3").arg(jegeo::px(fv)).arg(jegeo::py(fv)).arg(jegeo::polyline::is_hole(poly) ? "H" : "I");
        for (int i = 1 ; i < vertices.size(); ++i) {
            const auto &v = vertices[i];
            if (jegeo::vertex::is_lineto(v)) {
                res << QString("OS %1 %2").arg(jegeo::px(v)).arg(jegeo::py(v));
            }
            else if (jegeo::vertex::is_circle(v)) {
                const auto &pv = vertices[i - 1];
                res << QString("OC %1 %2 %3 %4 %5").arg(jegeo::px(pv)).arg(jegeo::py(pv)).arg(jegeo::px(v)).arg(jegeo::py(v)).arg(jegeo::angle(v) < 0 ? "Y" : "N");
            }
            else if (jegeo::vertex::is_arcto(v)) {
                const auto &pv = vertices[i - 1];
                auto xs = jegeo::px(pv);
                auto ys = jegeo::py(pv);
                auto xe = jegeo::px(v);
                auto ye = jegeo::py(v);
                T xc, yc;
                jegeo::math::arc_center_from_angle(xs, ys, xe, ye, jegeo::angle(v), xc, yc);
                res << QString("OC %1 %2 %3 %4 %5").arg(xe).arg(ye).arg(xc).arg(yc).arg(jegeo::angle(v) < 0 ? "Y" : "N");
            }
        }
        res << "OE";
    }
    res << "SE";
    return res;
}

template<typename T>
QPainterPath to_qpainterpath(const Polygon<T> &arcpolygon, double scale)
{
    QPainterPath res;
    for (const auto &path : arcpolygon) {
        res.addPath(polyline::to_qpainterpath(path, scale));
    }
    return res;
}

template<typename T>
Polygon<T>& transform(Polygon<T> &polygon, const QTransform &trans)
{
    for (auto &path : polygon) {
        polyline::transform(path, trans);
    }
    return polygon;
}

template<typename T>
Polygon<T> transformed(const Polygon<T> &polygon, const QTransform &trans)
{
    Polygon<T> res = polygon;
    transform(res, trans);
    return res;
}

} // namespace polygon


/***QDebug***/
template<typename T>
QDebug operator<<(QDebug dbg, const Point<T> &point)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "point(x:" << jegeo::px(point) << ", y:" << jegeo::py(point) << ")";
    return dbg;
}
template<typename T>
QDebug operator<<(QDebug dbg, const Line<T> &line)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "line/rect(x1:" << jegeo::psx(line) << ", y1:" << jegeo::psy(line) << ", x2:" << jegeo::pex(line) << ", y2:" << jegeo::pey(line) << ")";
    return dbg;
}
template<typename T>
QDebug operator<<(QDebug dbg, const Vertex<T> &vertex)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "vertex(x:" << jegeo::px(vertex) << ", y:" << jegeo::py(vertex) << ", angle:" << jegeo::angle(vertex) << ")";
    return dbg;
}
template<typename T>
QDebug operator<<(QDebug dbg, const Edge<T> &edge)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "edge(xs:" << jegeo::psx(edge) << ", ys:" << jegeo::psy(edge) << ", xe:" << jegeo::pex(edge) << ", ye:" << jegeo::pey(edge) << ", angle:" << jegeo::angle(edge);
    return dbg;
}
template<typename T>
QDebug operator<<(QDebug dbg, const Circle<T> &circle)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "circle(cx:" << jegeo::px(circle) << ", cy:" << jegeo::py(circle) << ", radius:" << jegeo::radius(circle) << ")";
    return dbg;
}
template<typename T>
QDebug operator<<(QDebug dbg, const Arc<T> &arc)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "arc(xs:" << jegeo::psx(arc) << ", ys:" << jegeo::psy(arc) << ", xe:" << jegeo::pex(arc) << ", ye:" << jegeo::pey(arc) << ", xc:" << jegeo::pcx(arc) << ", yc:" << jegeo::pcy(arc) << ", cw:" << jegeo::cw(arc) << ")";
    return dbg;
}

} // namespace jegeo

#endif // JEGEO_QTADAPTOR_H
